registry = {}


class Dispatcher(object):
    def __init__(self, name, arity):
        self.name = name
        self.arity = arity
        self.registry = []

    def __call__(self, *args):
        for priority, predicates, function in self.registry:
            if all(predicate(arg) for arg, predicate in zip(args, predicates)):
                return function(*args)
        raise TypeError('no match')

    def register(self, priority, predicates, function):
        def normalize(predicate):
            if type(predicate) is type:
                return lambda arg: isinstance(arg, predicate)
            elif hasattr(predicate, '__call__'):
                return predicate
            else:
                return lambda arg: predicate == arg
        predicates = [normalize(predicate) for predicate in predicates]
        self.registry.append((priority, predicates, function))
        self.registry.sort(lambda a, b: cmp(a[0], b[0]))


def multimethod(*predicates, **kwargs):
    def register(function):
        name = function.__name__
        arity = len(predicates)
        dispatcher = registry.get((name, arity))
        if dispatcher is None:
            dispatcher = registry[(name, arity)] = Dispatcher(name, arity)
        dispatcher.register(kwargs.get('priority', 0), predicates, function)
        return dispatcher
    return register


def test():
    @multimethod('Narrator')
    def last_name(first_name):
        return None

    @multimethod('Marla')
    def last_name(first_name):
        return 'Singer'

    @multimethod('Tyler')
    def last_name(first_name):
        return 'Durden'

    @multimethod(str)
    def last_name(first_name):
        return 'Doe'

    assert last_name('Narrator') is None
    assert last_name('Marla') == 'Singer'
    assert last_name('Tyler') == 'Durden'
    assert last_name('John') == 'Doe'


if __name__ == '__main__':
    test()
